package com.tianji.learning.service.impl;

import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author XinxuanZhuo
 * @since 2024-02-08
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {

    // 我的课表服务
    private final ILearningLessonService learningLessonService;


    // 远程课程服务
    private final CourseClient courseClient;

    // 提交学习记录延迟任务handler
    private final com.tianji.learning.util.LearningRecordDelayTaskHandler taskHandler;

    /**
     * 查询当前用户指定课程的学习进度
     *
     * @param courseId 课程id
     * @return 课表信息、学习记录及进度信息
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        // 获取当前用户
        Long userId = UserContext.getUser();
        // 根据课程id和用户id查询课表（learning_lesson表）
        LearningLesson lesson = learningLessonService.queryByUserIdAndCourseId(userId, courseId);
        if (lesson == null) {
            throw new BizIllegalException("课表中不存在当前课程");
        }
        // 拿最近学习小节id、课表id、用户id去查学习记录表
        List<LearningRecord> learningRecords = this.lambdaQuery()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getLessonId, lesson.getId())
                .list();

        // 封装返回dto
        LearningLessonDTO dto = new LearningLessonDTO();
        dto.setId(lesson.getId());
        dto.setLatestSectionId(lesson.getLatestSectionId());
        dto.setRecords(BeanUtils.copyList(learningRecords, LearningRecordDTO.class));
        return dto;
    }

    /**
     * 提交学习记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addLearningRecord(LearningRecordFormDTO recordDTO) {
        // 1.获取当前登录用户
        Long userId = UserContext.getUser();

        // 2.处理学习记录逻辑
        boolean sectionFinished = false;
        SectionType sectionType = recordDTO.getSectionType();
        if (SectionType.EXAM.equalsValue(sectionType.getValue())) {
            // 考试类型记录
            sectionFinished = handleExamRecord(userId, recordDTO);
        } else {
            // 播放视频类型记录
            sectionFinished = handleVideoRecord(userId, recordDTO);
        }
        if (!sectionFinished) {
            // 没有新学完的小节，无需更新课表中的学习进度
            return;
        }
        // 3.处理我的课表逻辑
        handleLearningLessonsChanges(recordDTO);
    }

    /**
     * 根据小节的学习情况，对当前用户课表进行处理
     * 判断课程是否全部学完，更新课表数据（最近学习时间，最近一次学习小节）
     */
    private void handleLearningLessonsChanges(LearningRecordFormDTO recordDTO) {
        // 1.查询课表信息
        Long lessonId = recordDTO.getLessonId();
        LearningLesson lesson = learningLessonService.getById(lessonId);
        // 2.课表为空抛异常
        if (lesson == null) {
            throw new BizIllegalException("该课程不存在课表，无法更新数据");
        }
        // 3.如果sectionFinished为true，远程调用课程服务，查询当前课程的所有小节，判断是否全部学完
        // 有完成的小节
        boolean allLearned = false;
        Long courseId = lesson.getCourseId();
        // 远程调用查询课程总节数
        CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(courseId, false, false);
        if (courseInfo == null) {
            throw new BizIllegalException("课程不存在，无法更新数据！");
        }
        Integer sectionNum = courseInfo.getSectionNum();
        Integer learnedSections = lesson.getLearnedSections();
        allLearned = learnedSections + 1 >= sectionNum;
        // 4.更新课表数据
        // 根据第三步的flag对课表有一个条件更新
        learningLessonService.lambdaUpdate()
                .set(lesson.getLearnedSections() == 0, LearningLesson::getStatus, LessonStatus.LEARNING)
                .set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED.getValue())
                .set(LearningLesson::getLatestSectionId, recordDTO.getSectionId())
                .set(LearningLesson::getLatestLearnTime, recordDTO.getCommitTime())
                .setSql("learned_sections = learned_sections + 1")
                .eq(LearningLesson::getId, lesson.getId())
                .update();
    }

    /**
     * 处理视频学习记录
     * 视频播放进度超过50则完成学习
     */
    private Boolean handleVideoRecord(Long userId, LearningRecordFormDTO recordDTO) {
        // 查询旧的学习记录
        LearningRecord oldRecord = queryOldRecord(recordDTO.getLessonId(), recordDTO.getSectionId());

        // 是否存在  分支：1.不存在->第一次学习 新增 2.存在学习进度判断 更新
        // 不存在
        if (oldRecord == null) {
            // 新增新的视频学习记录
            LearningRecord newRecord = BeanUtils.copyBean(recordDTO, LearningRecord.class);
            //填充数据
            newRecord.setUserId(userId);
            // 写入数据库
            boolean success = save(newRecord);
            if (!success) {
                throw new DbException("新增学习记录失败！");
            }
            return false;
        }
        // 存在，判断是否已经学习完了，finished状态为false，视频播放时间不超过 50%（是否第一次学完--> 原本是false）
        boolean isFinished = !oldRecord.getFinished() && oldRecord.getMoment() * 2 >= recordDTO.getDuration();
        // 不是第一次学完，新增到延时队列任务
        if (!isFinished) {
            LearningRecord record = new LearningRecord();
            record.setLessonId(recordDTO.getLessonId());
            record.setSectionId(recordDTO.getSectionId());
            record.setMoment(recordDTO.getMoment());
            record.setId(oldRecord.getId());
            record.setFinished(oldRecord.getFinished());
            taskHandler.addLearningRecordTask(record);
            return false;
        }
        // 第一次学完，更新数据，清除redis旧的学习记录缓存，因为旧的缓存中的isFinished字段是flase
        this.lambdaUpdate()
                .set(LearningRecord::getMoment, recordDTO.getMoment())
                .set(LearningRecord::getFinished, true)
                .set(LearningRecord::getFinishTime, recordDTO.getCommitTime())
                .eq(LearningRecord::getId, oldRecord.getId())
                .update();
        // 清除缓存
        taskHandler.cleanRecordCache(recordDTO.getLessonId(), recordDTO.getSectionId());
        return true;
    }

    /**
     * 查询旧的学习记录
     * redis缓存命中直接返回，否则查数据库放缓存再返回
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        // 查询缓存
        LearningRecord oldRecord = taskHandler.readRecordCache(lessonId, sectionId);
        // 命中返回
        if (oldRecord != null) {
            return oldRecord;
        }
        // 未命中查DB
        LearningRecord oldRecordFromDB = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .one();
        if (oldRecordFromDB == null) {
            return null;
        }
        // 放缓存返回
        taskHandler.writeRecordCache(oldRecordFromDB);
        return oldRecordFromDB;
    }


    /**
     * 处理考试学习记录
     */
    private Boolean handleExamRecord(Long userId, LearningRecordFormDTO recordDTO) {
        // dto转po
        LearningRecord learningRecord = BeanUtils.copyBean(recordDTO, LearningRecord.class);
        // 学习记录表的 po插入操作
        learningRecord.setUserId(userId);
        learningRecord.setFinished(true);
        learningRecord.setFinishTime(recordDTO.getCommitTime());
        // 写入数据库
        boolean success = save(learningRecord);
        if (!success) {
            throw new DbException("新增考试记录失败！");
        }
        // 提交了考试就代表完成学习
        return true;
    }


}
